Skip to content

Unify view create/edit via right-side Panel, replace dual implementation - #688

Merged
hotlong merged 4 commits into
mainfrom
copilot/unify-add-edit-view-panel
Feb 21, 2026
Merged

Unify view create/edit via right-side Panel, replace dual implementation#688
hotlong merged 4 commits into
mainfrom
copilot/unify-add-edit-view-panel

Conversation

Copilot AI commented Feb 21, 2026

Copy link
Copy Markdown
Contributor

New and edit view flows used two separate implementations (full-screen ViewDesigner vs. sidebar ViewConfigPanel) with overlapping features but different data models and UX. This unifies them into a single panel-based flow.

UnifiedViewConfig type (@object-ui/types)

  • New interface combining flat view properties with nested type-specific options (kanban, calendar, map, gallery, timeline, gantt, chart)
  • Serves as the canonical type for both panel and designer

ViewConfigPanel — mode prop + type-specific options

  • mode="create" | "edit" (default "edit") — create mode initializes empty draft with isDirty=true
  • onCreate callback separate from onSave for create flow
  • Type-specific options section renders conditionally per view type:
<ViewConfigPanel
  open={true}
  mode="create"
  activeView={activeView}
  objectDef={objectDef}
  onCreate={(config) => dataSource.create('sys_view', config)}
  onSave={(draft) => dataSource.updateViewConfig(objectName, draft.id, draft)}
/>
  • Kanban: groupByField
  • Calendar: startDateField, titleField
  • Map: latitudeField, longitudeField
  • Gallery: imageField
  • Timeline/Gantt: dateField, titleField

ObjectView — rewired entry points

  • "Add View" (dropdown + ViewTabBar) opens panel in mode="create" instead of navigating to ViewDesigner
  • ViewDesigner retained as "Advanced Editor" behind a separate menu item
  • Added viewConfigPanelMode state and handleViewCreate callback

i18n + ROADMAP

  • Added keys: createView, newView, advancedEditor, typeOptions, field labels
  • Updated P1.8 roadmap section with completed items

Tests

  • 14 new tests covering create mode and type-specific options (49 total ViewConfigPanel)
  • Updated 2 ObjectView tests for panel-based flow, added 1 for Advanced Editor
  • ViewDesigner (37) and types (109) tests unaffected
Original prompt

This section details on the original issue you should resolve

<issue_title>前端视图新增/编辑界面统一,采用右侧Panel模式替代两套实现</issue_title>
<issue_description>## 需求背景
当前前端"新增视图"与"编辑视图"采用了两套不同的界面和实现方式(ViewDesigner与ViewConfigPanel),导致代码与用户体验非常混乱。功能高度重叠、数据格式和保存逻辑都不同。

问题总结

  • 新增视图时进入全屏ViewDesigner(3栏),编辑视图则用右侧ViewConfigPanel(侧边栏)
  • 两者字段配置、过滤、排序、视图类型等功能几乎一样,但UI呈现和数据结构不同
  • 若都保留则必须明确场景、入口和用户定位

方案建议

参考Airtable等设计,建议统一采用"右侧Panel"(ViewConfigPanel)作为新增和编辑视图的唯一入口,实现如下:

  1. 新增/编辑视图都通过右侧Panel,mode="create"或"edit"
  2. 合并ViewDesigner的type-specific options(如Kanban/Calendar/Map配置)至Panel
  3. 数据模型统一为UnifiedViewConfig,保存API统一
  4. 保留全屏ViewDesigner作为高级编辑入口,��供高级用户

优势

  • 用户体验一致,新增/编辑都在当前页完成
  • 开发成本大幅降低,维护性提升
  • 减少重复代码,统一保存逻辑和数据结构

关键开发任务

  • 统一数据结构(columns等用ViewDesignerColumn对象)
  • 扩展ViewConfigPanel支持所有视图类型(type-specific options)
  • 使用Panel替代全屏ViewDesigner,作为默认入口
  • 支持拖拽排序(dnd-kit)等高级功能在Panel
  • 编辑/新增入口和导航规范化
  • 保留ViewDesigner为"高级模式",入口弱化
  • 统一保存API和回调
  • ROADMAP同步更新

影响范围

  • ObjectView主流程
  • ViewDesigner和ViewConfigPanel
  • 路由、数据模型、保存逻辑
  • 用户界面体验

如需详细拆分子任务请补充说明。</issue_description>

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

@vercel

vercel Bot commented Feb 21, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
objectui Ready Ready Preview, Comment Feb 21, 2026 7:07am
objectui-demo Ready Ready Preview, Comment Feb 21, 2026 7:07am
objectui-storybook Ready Ready Preview, Comment Feb 21, 2026 7:07am

Request Review

Copilot AI and others added 3 commits February 21, 2026 06:45
…ype-specific options

- Add UnifiedViewConfig type to @object-ui/types
- Add mode="create"|"edit" prop to ViewConfigPanel
- Add type-specific options (kanban/calendar/map/gallery/timeline/gantt)
- Add onCreate callback for create mode
- Update ObjectView: "Add View" opens panel in create mode
- Keep ViewDesigner as "Advanced Editor" entry
- Add i18n keys for new features
- Add 14 new tests (49 total passing)

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
- Update "Add View" tests to verify panel opens instead of navigation
- Add test for "Advanced Editor" button navigation
- Update ROADMAP.md with completed P1.8 items

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copilot AI changed the title [WIP] Refactor add and edit views to use right-side panel Unify view create/edit via right-side Panel, replace dual implementation Feb 21, 2026
Copilot AI requested a review from hotlong February 21, 2026 06:54
@hotlong
hotlong marked this pull request as ready for review February 21, 2026 06:54
Copilot AI review requested due to automatic review settings February 21, 2026 06:54
@hotlong
hotlong merged commit 40381fd into main Feb 21, 2026
4 of 6 checks passed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR unifies the view creation and editing experience by consolidating two separate implementations (full-screen ViewDesigner vs. sidebar ViewConfigPanel) into a single panel-based flow. The changes align with Airtable-style UX patterns and maintain backward compatibility while significantly improving code maintainability.

Changes:

  • Introduced UnifiedViewConfig type in @object-ui/types that combines flat view properties with nested type-specific options for kanban, calendar, map, gallery, timeline, gantt, and chart views
  • Extended ViewConfigPanel with mode="create"|"edit" prop and type-specific configuration sections, allowing it to handle both view creation and editing flows
  • Rewired ObjectView entry points so "Add View" opens the config panel instead of navigating to ViewDesigner, while retaining ViewDesigner as "Advanced Editor"

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
packages/types/src/index.ts Exports new UnifiedViewType and UnifiedViewConfig types
packages/types/src/designer.ts Defines UnifiedViewConfig interface with type-specific nested options per @objectstack/spec protocol
packages/i18n/src/locales/en.ts Adds English translations for create mode UI and type-specific field labels
apps/console/src/components/ViewConfigPanel.tsx Implements mode prop, onCreate callback, and conditional type-specific options rendering
apps/console/src/components/ObjectView.tsx Replaces navigation-based "Add View" with panel opening; adds "Advanced Editor" menu item
apps/console/src/tests/ViewConfigPanel.test.tsx Adds 14 new tests covering create mode and type-specific options (49 total)
apps/console/src/tests/ObjectView.test.tsx Updates 2 tests for panel flow, adds 1 test for Advanced Editor navigation
ROADMAP.md Marks P1.8 items as completed (unified create/edit, type-specific options, etc.)

Comment on lines 281 to +284
useEffect(() => {
setDraft({ ...activeView });
setIsDirty(false);
}, [activeView.id]);
setDraft({ ...effectiveActiveView });
setIsDirty(mode === 'create');
}, [mode, activeView.id]); // eslint-disable-line react-hooks/exhaustive-deps

Copilot AI Feb 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The useEffect has an eslint-disable comment for exhaustive-deps, but is missing effectiveActiveView in the dependency array. Since effectiveActiveView is computed from defaultNewView (which depends on mode being 'create'), changes to mode should trigger re-evaluation, but the effect won't pick up changes to the memoized defaultNewView content itself. Consider either adding effectiveActiveView to the deps or ensuring the memo dependencies are correct.

Copilot uses AI. Check for mistakes.
Comment on lines +217 to +229
createView: 'Create View',
newView: 'New View',
advancedEditor: 'Advanced Editor',
typeOptions: 'Type Options',
groupByField: 'Group by field',
startDateField: 'Start date field',
titleField: 'Title field',
latitudeField: 'Latitude field',
longitudeField: 'Longitude field',
imageField: 'Image field',
dateField: 'Date field',
selectField: 'Select field...',
gridOptionsHint: 'Grid view uses the columns configured above.',

Copilot AI Feb 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new i18n keys added to the English locale (createView, newView, advancedEditor, typeOptions, groupByField, startDateField, titleField, latitudeField, longitudeField, imageField, dateField, selectField, gridOptionsHint) are missing from other locale files (zh, ar, de, es, fr, ja, ko, pt, ru). This will cause untranslated keys to appear in non-English locales.

Copilot uses AI. Check for mistakes.

// Default empty view for create mode
const defaultNewView = useMemo(() => ({
id: `view_${Date.now()}`,

Copilot AI Feb 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using Date.now() for ID generation can lead to collisions if multiple views are created in rapid succession. Consider using crypto.randomUUID() instead, which is already used elsewhere in the codebase for generating unique IDs (as seen in the filter/sort builder code).

Suggested change
id: `view_${Date.now()}`,
id: `view_${crypto.randomUUID()}`,

Copilot uses AI. Check for mistakes.
Comment on lines +94 to +106
const handleViewCreate = useCallback(async (config: Record<string, any>) => {
try {
if (dataSource?.create) {
const payload = { objectName, ...config };
await dataSource.create('sys_view', payload);
}
setShowViewConfigPanel(false);
setViewConfigPanelMode('edit');
setRefreshKey(k => k + 1);
} catch (err) {
console.error('[ViewConfigPanel] Failed to create view:', err);
}
}, [dataSource, objectName]);

Copilot AI Feb 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The handleViewCreate function doesn't provide user feedback upon successful view creation or when an error occurs. Consider adding toast notifications (success/error) to inform users about the operation outcome, similar to the pattern used in ViewDesignerPage.

Copilot uses AI. Check for mistakes.
Comment on lines +527 to +660
{viewType !== 'grid' && (
<>
<SectionHeader title={t('console.objectView.typeOptions')} />
<div data-testid="type-options-section" className="space-y-2">
{viewType === 'kanban' && (
<div>
<span className="text-xs text-muted-foreground">{t('console.objectView.groupByField')}</span>
<select
data-testid="type-opt-kanban-groupByField"
className="w-full text-xs h-7 rounded-md border border-input bg-background px-2 text-foreground mt-1"
value={draft.kanban?.groupByField || draft.kanban?.groupField || ''}
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => handleTypeOptionChange('kanban', 'groupByField', e.target.value)}
>
<option value="">{t('console.objectView.selectField')}</option>
{fieldOptions.map(f => (
<option key={f.value} value={f.value}>{f.label}</option>
))}
</select>
</div>
)}
{viewType === 'calendar' && (
<>
<div>
<span className="text-xs text-muted-foreground">{t('console.objectView.startDateField')}</span>
<select
data-testid="type-opt-calendar-startDateField"
className="w-full text-xs h-7 rounded-md border border-input bg-background px-2 text-foreground mt-1"
value={draft.calendar?.startDateField || ''}
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => handleTypeOptionChange('calendar', 'startDateField', e.target.value)}
>
<option value="">{t('console.objectView.selectField')}</option>
{fieldOptions.map(f => (
<option key={f.value} value={f.value}>{f.label}</option>
))}
</select>
</div>
<div>
<span className="text-xs text-muted-foreground">{t('console.objectView.titleField')}</span>
<select
data-testid="type-opt-calendar-titleField"
className="w-full text-xs h-7 rounded-md border border-input bg-background px-2 text-foreground mt-1"
value={draft.calendar?.titleField || ''}
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => handleTypeOptionChange('calendar', 'titleField', e.target.value)}
>
<option value="">{t('console.objectView.selectField')}</option>
{fieldOptions.map(f => (
<option key={f.value} value={f.value}>{f.label}</option>
))}
</select>
</div>
</>
)}
{viewType === 'map' && (
<>
<div>
<span className="text-xs text-muted-foreground">{t('console.objectView.latitudeField')}</span>
<select
data-testid="type-opt-map-latitudeField"
className="w-full text-xs h-7 rounded-md border border-input bg-background px-2 text-foreground mt-1"
value={draft.map?.latitudeField || ''}
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => handleTypeOptionChange('map', 'latitudeField', e.target.value)}
>
<option value="">{t('console.objectView.selectField')}</option>
{fieldOptions.map(f => (
<option key={f.value} value={f.value}>{f.label}</option>
))}
</select>
</div>
<div>
<span className="text-xs text-muted-foreground">{t('console.objectView.longitudeField')}</span>
<select
data-testid="type-opt-map-longitudeField"
className="w-full text-xs h-7 rounded-md border border-input bg-background px-2 text-foreground mt-1"
value={draft.map?.longitudeField || ''}
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => handleTypeOptionChange('map', 'longitudeField', e.target.value)}
>
<option value="">{t('console.objectView.selectField')}</option>
{fieldOptions.map(f => (
<option key={f.value} value={f.value}>{f.label}</option>
))}
</select>
</div>
</>
)}
{viewType === 'gallery' && (
<div>
<span className="text-xs text-muted-foreground">{t('console.objectView.imageField')}</span>
<select
data-testid="type-opt-gallery-imageField"
className="w-full text-xs h-7 rounded-md border border-input bg-background px-2 text-foreground mt-1"
value={draft.gallery?.imageField || ''}
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => handleTypeOptionChange('gallery', 'imageField', e.target.value)}
>
<option value="">{t('console.objectView.selectField')}</option>
{fieldOptions.map(f => (
<option key={f.value} value={f.value}>{f.label}</option>
))}
</select>
</div>
)}
{(viewType === 'timeline' || viewType === 'gantt') && (
<>
<div>
<span className="text-xs text-muted-foreground">{t('console.objectView.dateField')}</span>
<select
data-testid={`type-opt-${viewType}-dateField`}
className="w-full text-xs h-7 rounded-md border border-input bg-background px-2 text-foreground mt-1"
value={draft[viewType]?.dateField || draft[viewType]?.startDateField || ''}
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => handleTypeOptionChange(viewType, 'dateField', e.target.value)}
>
<option value="">{t('console.objectView.selectField')}</option>
{fieldOptions.map(f => (
<option key={f.value} value={f.value}>{f.label}</option>
))}
</select>
</div>
<div>
<span className="text-xs text-muted-foreground">{t('console.objectView.titleField')}</span>
<select
data-testid={`type-opt-${viewType}-titleField`}
className="w-full text-xs h-7 rounded-md border border-input bg-background px-2 text-foreground mt-1"
value={draft[viewType]?.titleField || ''}
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => handleTypeOptionChange(viewType, 'titleField', e.target.value)}
>
<option value="">{t('console.objectView.selectField')}</option>
{fieldOptions.map(f => (
<option key={f.value} value={f.value}>{f.label}</option>
))}
</select>
</div>
</>
)}
</div>
</>

Copilot AI Feb 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "chart" view type is included in VIEW_TYPE_OPTIONS and can be selected in the view type dropdown, but there is no corresponding type-specific options UI section for chart configuration (unlike kanban, calendar, map, gallery, timeline, gantt). This means users can select "chart" as a view type but cannot configure any chart-specific options (chartType, xAxisField, yAxisFields, etc.) through the panel. Consider either: (1) removing "chart" from VIEW_TYPE_OPTIONS if it's not yet supported in the panel UI, or (2) adding a basic chart configuration section, or (3) adding a comment explaining that chart configuration is intentionally omitted due to complexity.

Copilot uses AI. Check for mistakes.
showSearch: true,
showFilters: true,
showSort: true,
}), []);

Copilot AI Feb 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The useMemo hook for defaultNewView is missing the t function in its dependency array. The t function from useObjectTranslation() is used inside the memoization but not listed as a dependency, which could cause stale closure issues if the translation function changes.

Suggested change
}), []);
}), [t]);

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

前端视图新增/编辑界面统一,采用右侧Panel模式替代两套实现

3 participants